fix(rescan): retain modTimes when purge emit fails#210
Merged
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #207 — a rescan tick that detected a file is gone deleted its entry from
r.modTimesbeforereconcileDeletedactually emitted the purge snapshot. Ifemitter.Emitthen failed (SQLITE_BUSY exhaustingTxretry budget, context cancellation racing the emit, transient I/O), the in-memory tracking was already cleared but the DB rows remained. The deletion was never retried, leaving zombie nodes that surfaced viaMemorySearch, snapshot replays, etc. — until a full re-compile.Fix (issue option a — defer the delete): mirror the discipline already present in the compile path at
rescan.go:334(maps.Copy(r.modTimes, pending)runs aftercompiler.Compilereturns no error). Collect(absPath, relPath)pairs without mutatingr.modTimesin the loop; clearr.modTimesonly whenreconcileDeletedreturnsok=true.reconcileDeletedsignature:[]PurgedFile→([]PurgedFile, bool).ok=truefor the three no-op success branches (empty deleted, no matching nodes, happy path);ok=falseforGetNodesByFilesandEmiterrors.notifyChange()gated onok && len(deleted) > 0— failed purges don't fire change events.errorper.claude/rules/go-concise.md§5 ("handle or return, never both"): caller doesn't discriminate failure modes, andreconcileDeletedalready logs the underlying error internally.Test plan
TestRescanLoop_RetainsModTimesOnPurgeEmitFailure— new; mirrors siblingTestRescanLoop_SkipsPurgeOnWalkError. Injectsr.walkFnto wrap the real walk + cancel ctx after the walk returns, soemitter.Emit'sBeginTx(ctx, ...)fails. Asserts modTimes preserved + DB nodes intact + no new snapshot row. Then resetswalkFnand re-scans to verify the retry path completes the purge.TestRescanLoop_ReconcilesDeletedFiles,TestRescanLoop_PublishesStatus,TestRescanLoop_SkipsPurgeOnWalkError— still green (no regression in happy / walk-error paths).make test,make fmt lint tidy— all green (0 lint issues).Reviewed by
go-style-reviewer(project subagent) + Codex (generic, via/forge ... codex). Loop converged on first pass — zero actionable findings.🤖 Generated with Claude Code